use crate::imp::tonicrpc::kcptunnel::tunnel::{IpInLanType, TunnelPairWithIp};
use std::collections::HashMap;
use std::sync::RwLock;
// use fastping_rs::PingResult::{self,Idle, Receive};
// use fastping_rs::Pinger;
use futures_util::StreamExt;

use crate::model::sdk_api_result::{DEFAULT_CONNECTION_TIMEOUT_SECS, DEFAULT_REQUEST_TIMEOUT_SECS};
use std::time::Duration;

lazy_static! {
    static ref PAIR_IN_LAN_MAP: RwLock<HashMap<TunnelPairWithIp, bool>> =
        RwLock::new(HashMap::new());
}

// Add current pair (local_client_id & remote_client_id) in a map,set lan flag =true
pub fn add_pair_in_lan_map(pair: TunnelPairWithIp, is_in_lan: bool) {
    {
        let mut pair_in_lan_map = PAIR_IN_LAN_MAP.write().unwrap();
        (*pair_in_lan_map)
            .drain_filter(|k, _v| k.pair.pair_remote_client_id == pair.pair.pair_remote_client_id);
        (*pair_in_lan_map).insert(pair, is_in_lan);
    }
}

pub fn sure_in_lan(remote_id: String) {
    {
        let mut find_key = None;
        let mut pair_in_lan_map = PAIR_IN_LAN_MAP.write().unwrap();
        for key in (*pair_in_lan_map).keys() {
            if key.pair.pair_remote_client_id == remote_id {
                find_key = Some(key.clone());
                break;
            }
        }
        if find_key.is_some() {
            let mut new_key = find_key.unwrap().clone();
            new_key.ip_in_lan_type = IpInLanType::SURE;
            (*pair_in_lan_map).drain_filter(|k, _v| k.pair.pair_remote_client_id == remote_id);
            (*pair_in_lan_map).insert(new_key, true);
        }
    }
}

// remove current pair (local_client_id & remote_client_id) from map,key = tunnel_id
pub fn remove_pair_in_lan_map_by_tunnel_id(tunnel_id: String) {
    {
        let mut pair_in_lan_map = PAIR_IN_LAN_MAP.write().unwrap();
        (*pair_in_lan_map).drain_filter(|k, _v| k.pair.tunnel_id == tunnel_id);
    }
}

pub fn remove_pair_in_lan_map_by_remote_id(remote_id: String) {
    {
        let mut pair_in_lan_map = PAIR_IN_LAN_MAP.write().unwrap();
        (*pair_in_lan_map).drain_filter(|k, _v| k.pair.pair_remote_client_id == remote_id);
    }
}

// remove all pair (local_client_id & remote_client_id) from map
pub fn remove_all_in_lan_map() {
    {
        let mut pair_in_lan_map = PAIR_IN_LAN_MAP.write().unwrap();
        (*pair_in_lan_map).clear();
    }
}

pub fn is_remote_in_not_need_reply(remote_id: String) -> bool {
    let mut pair_with_ip_opt = None;
    let mut is_in_lan_map = false;
    let mut ip_in_lan_type = IpInLanType::NOT;
    {
        let pair_in_lan_map = PAIR_IN_LAN_MAP.read().unwrap();
        for (key, val) in (*pair_in_lan_map).iter() {
            if key.pair.pair_remote_client_id == remote_id {
                pair_with_ip_opt = Some(key.clone());
                is_in_lan_map = *val;
                ip_in_lan_type = key.ip_in_lan_type.clone();
                break;
            }
        }
    }

    let mut bool_flag = false;
    if is_in_lan_map && ip_in_lan_type != IpInLanType::NOT {
        let pair_with_ip = pair_with_ip_opt.unwrap();
        let my_ip_opt = pair_with_ip.mine_ip.clone();
        let their_ip_opt = pair_with_ip.remote_ip.clone();

        if my_ip_opt.is_some() && their_ip_opt.is_some() {
            let my_ip = my_ip_opt.unwrap();
            let my_ip_split = my_ip.split(".");
            let my_ip_vec = my_ip_split.collect::<Vec<&str>>();

            let their_ip = their_ip_opt.unwrap();
            let their_ip_split = their_ip.split(".");
            let their_ip_vec = their_ip_split.collect::<Vec<&str>>();

            if my_ip_vec.len() >= 3 && their_ip_vec.len() >= 3 {
                if my_ip_vec[0] == their_ip_vec[0] && my_ip_vec[0] == "10" {
                    bool_flag = true;
                } else if my_ip_vec[0] == their_ip_vec[0]
                    && my_ip_vec[1] == their_ip_vec[1]
                    && my_ip_vec[0] == "172"
                {
                    bool_flag = true;
                } else if my_ip_vec[0] == their_ip_vec[0]
                    && my_ip_vec[1] == their_ip_vec[1]
                    && my_ip_vec[2] == their_ip_vec[2]
                {
                    bool_flag = true;
                }
            }
        }
    }
    bool_flag
}

// check if remote_id in map if flag set to lan
pub fn is_remote_in_lan_map(remote_id: String) -> Option<IpInLanType> {
    let mut is_in_lan_map = false;
    let mut ip_in_lan_type = IpInLanType::NOT;
    {
        let pair_in_lan_map = PAIR_IN_LAN_MAP.read().unwrap();
        for (key, val) in (*pair_in_lan_map).iter() {
            if key.pair.pair_remote_client_id == remote_id {
                is_in_lan_map = *val;
                ip_in_lan_type = key.ip_in_lan_type.clone();
                break;
            }
        }
    }

    if !is_in_lan_map {
        None
    } else {
        Some(ip_in_lan_type)
    }
}

// get remote ip address from remote map by key is remote_id
pub fn get_remote_in_lan_host(remote_id: String, remote_lan_port: u16) -> Option<String> {
    let is_in_lan_type = is_remote_in_lan_map(remote_id.clone());
    if is_in_lan_type.is_some() {
        let lan_type = is_in_lan_type.unwrap();
        match lan_type {
            IpInLanType::NOT => {
                log::warn!(
                    "get_remote_in_lan_host remote:{:?} IpInLanType::NOT",
                    remote_id
                );

                return None;
            }
            IpInLanType::SURE => {
                log::warn!(
                    "get_remote_in_lan_host remote:{:?} IpInLanType::SURE",
                    remote_id
                );
                let pair_in_lan_map = PAIR_IN_LAN_MAP.read().unwrap();
                let mut remote_ip_opt = None;
                for (key, _val) in (*pair_in_lan_map).iter() {
                    if key.pair.pair_remote_client_id == remote_id {
                        remote_ip_opt = key.remote_ip.clone();
                        break;
                    }
                }
                return remote_ip_opt;
            }
            IpInLanType::MAYBE => {
                log::warn!(
                    "get_remote_in_lan_host remote:{:?} IpInLanType::MAYBE",
                    remote_id
                );
                let mut saved_remote_ip = None;
                {
                    let pair_in_lan_map = PAIR_IN_LAN_MAP.read().unwrap();
                    for (key, _val) in (*pair_in_lan_map).iter() {
                        if key.pair.pair_remote_client_id == remote_id {
                            saved_remote_ip = key.remote_ip.clone();
                            break;
                        }
                    }
                }
                if saved_remote_ip.is_some() {
                    let remote_ip = saved_remote_ip.unwrap();
                    std::thread::spawn(move || {
                        let net_type_check_url =
                            format!("http://{}:{}/hi/ping", remote_ip, remote_lan_port);
                        // let client = Client::new();
                        match reqwest::blocking::Client::builder()
                            .connect_timeout(Duration::from_secs(DEFAULT_CONNECTION_TIMEOUT_SECS))
                            .gzip(true)
                            // .trust_dns(true)
                            .build()
                        {
                            Ok(client) => {
                                match client
                                    .get(net_type_check_url.clone())
                                    .timeout(std::time::Duration::from_secs(
                                        DEFAULT_REQUEST_TIMEOUT_SECS,
                                    ))
                                    .send()
                                {
                                    Ok(resp) => {
                                        if resp.status().is_success() {
                                            log::info!(
                                                "get_remote_in_lan_host ping:{:?} success",
                                                net_type_check_url.to_owned()
                                            );
                                            sure_in_lan(remote_id);
                                        } else {
                                            log::error!("get_remote_in_lan_host call {:?} error status:{:?}",net_type_check_url.clone(),resp.status());
                                        }
                                    }
                                    Err(err) => {
                                        log::error!(
                                            "get_remote_in_lan_host call {:?} error:{:?}",
                                            net_type_check_url.clone(),
                                            err
                                        );
                                    }
                                }
                            }
                            Err(err) => {
                                log::error!(
                                    "get_remote_in_lan_host call {:?} error:{:?}",
                                    net_type_check_url.clone(),
                                    err
                                );
                            }
                        }
                    });
                }
                return None;
            }
        }
    }
    return None;
}

// if webrtc state change to connected ,and add ip to map.
pub fn connected_pair_ip_map(pair_with_ip: TunnelPairWithIp) {
    if pair_with_ip.remote_ip.is_some() {
        let remote_ip = pair_with_ip.clone().remote_ip.unwrap();

        match pair_with_ip.ip_in_lan_type {
            IpInLanType::NOT => {
                log::info!(
                    "connected_pair_ip_map sure not in lan and remote_ip = {:?}",
                    remote_ip.to_owned()
                );
                // if current status is connected but remote type is not host
                // then delete remote ip by remote_id
                let remote_id = pair_with_ip.pair.pair_remote_client_id.to_owned();
                remove_pair_in_lan_map_by_remote_id(remote_id);
            }
            IpInLanType::SURE => {
                log::info!(
                    "connected_pair_ip_map sure in lan and remote_ip = {:?}",
                    remote_ip.to_owned()
                );
                add_pair_in_lan_map(pair_with_ip.to_owned(), true);
            }
            IpInLanType::MAYBE => {
                log::info!(
                    "connected_pair_ip_map maybe in lan and remote_ip = {:?}",
                    remote_ip.to_owned()
                );
                add_pair_in_lan_map(pair_with_ip.to_owned(), true);
            }
        }
    } else {
        log::error!("connected_pair_ip_map error because pair_with_ip.remote_ip.is_none!");
    }
}
